feat(onboarding): mnemonic-entry refactor + ASB UX hardening#507
Open
yasin-ce wants to merge 11 commits into
Open
feat(onboarding): mnemonic-entry refactor + ASB UX hardening#507yasin-ce wants to merge 11 commits into
yasin-ce wants to merge 11 commits into
Conversation
Replaces the small `PWRoundIcon` chip with a 4×xxl direct-SVG render of shield-check, matching the hero-illustration scale used by every other onboarding info screen (WatchInfoScreen, ImportInfoScreen, the backup family). Tints via `theme.colors.textMain` since shield-check.svg uses `currentColor`, so it adapts to light/dark mode automatically. Adds a `vi.mock` for the SVG in the integration setup — jsdom chokes on the long data URL `svgr` emits otherwise.
`BackupVerificationScreen` shows the quiz tiles whose `options` contain the real mnemonic words (mixed with decoys). Even though the user hasn't picked the correct order yet, an attacker who captures the screen learns the candidate set. Aligns with the screen-capture prevention already applied to `BackupReminderMnemonicScreen` and `ViewPassphraseContent`.
Pulls the mnemonic-input mechanic out of ImportAccountScreen (where it
was an ~180-line slab of paste-distribution, clipboard fallback, and
suggestion derivation) into a reusable hook + util + component, then
adopts them across both import flows:
- `useMnemonicWordEntry` — focus management, multi-separator paste
distribution, iOS-autocomplete-safe clipboard fallback,
wordlist-driven suggestions. Wordlist- and copy-agnostic; callers
pass `onTooManyWords` / `onInsufficientSlots` so screen-specific
i18n stays out.
- `splitMnemonic` — accepts any mix of whitespace + commas.
- `MnemonicSuggestionBar` — sticky horizontal pill bar that renders
nothing when there are no suggestions, replaces the old
per-row `WordSuggestionDropdown` (deleted).
Same diff also lands four orthogonal hardenings that touch the same
screens, so they're bundled here rather than fragmented:
- Screen-capture prevention on AsbImportKeyScreen and
ImportAccountScreen via `usePreventScreenCapture`.
- Success-path uses `navigation.replace` so the typed mnemonic in the
input hook's state is dropped for GC when the screen unmounts;
back-nav from later steps no longer lands on a prefilled screen.
- AsbImportKey redirects to the file-pick step when `envelope` is
null (defensive; covers the rare flow where the store gets wiped
while the screen is mounted).
- Replaces the `HEADER_HEIGHT = 50` magic constant with
`useHeaderHeight()` from `@react-navigation/elements` for the
`KeyboardAvoidingView.keyboardVerticalOffset` on both screens.
Includes vitest mocks for `@react-navigation/elements.useHeaderHeight`
(throws under the headless test navigator) and unit/component/integration
test coverage for the new primitives.
After a successful import, SelectAccounts cleanup runs `reset()` on the flow store to zero decrypted private keys. If the user then navigates back into the backup screen (Android system back from Result, etc.), the local "Pasted backup" card kept rendering because `loadedFile` is React state separate from the store. Tapping Next jumped to a Key screen with no envelope, which bounced back here — a silent loop. Sync `loadedFile` to `envelope`: when the store is wiped externally, clear the local indicator so Continue properly disables and the user has to re-pick / re-paste. Adds a regression integration test that drives the wipe through the same `useAsbImportFlowStore.reset()` SelectAccounts cleanup performs (no need to simulate the full stack-nav cascade through Result).
Pressing the on-screen keyboard's action button on the 12-word ASB key and 24-word HD import screens now jumps to the next input. Inputs 1..N-1 expose returnKeyType="next" with blurOnSubmit=false so the keyboard stays up while focus advances; the last input exposes returnKeyType="done" and blurs on submit. The ref array, focus-on-focused effect, and submit handler all live in useMnemonicWordEntry so both consumers wire identically through refCallbacks + handleSubmitEditing. The effect skips its mount run to avoid fighting the screens' autoFocus on slot 0.
The hook calls navigation.replace (so the input screen unmounts and its typed mnemonic is dropped for GC); the test was asserting against push and silently failing.
splitMnemonic strips commas, but the prior check normalized only
whitespace. A keyboard that appended a comma after accepting a
suggestion ("abandon,") then failed the wordlist match, the
clipboard fallback engaged, and any mnemonic on the clipboard
overwrote every slot. Derive the candidate token from
splitMnemonic so trailing punctuation is stripped before the
wordlist check.
…cope useImportAccountScreen.canImport intentionally only checks non-empty slots — strict wordlist validation lives in useImportAccount and surfaces typed errors via toast. ASB pre-validates because a wrong mnemonic decrypts to opaque garbage rather than a typed error.
Move type-only imports into the canonical trailing block in useMnemonicWordEntry and useAsbImportKeyScreen so type-vs-value groups are visually distinct. Also annotate the finally clause in handleContinue: no explicit wipe is a deliberate UX call — success path drops the reference via navigation.replace, error path preserves words so the user can fix a typo without re-typing 12 words.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stacked on #500. Four logically grouped commits cleaning up the ASB import flow:
style(onboarding):hero-scale shield illustration onAsbImportInfoScreen, replacing the smallPWRoundIconchip with a direct-SVG render at the same scale asWatchInfoScreen/ImportInfoScreen.feat(security):usePreventScreenCaptureonBackupVerificationScreen(quiz tiles include the real mnemonic words mixed with decoys).feat(onboarding):extractuseMnemonicWordEntry+MnemonicSuggestionBar+splitMnemonicfrom the 180-line slab insideImportAccountScreen, adopted across bothImportAccountScreenandAsbImportKeyScreen. Same diff lands four hardenings on the two screens: screen-capture prevention,navigation.replaceon success paths (typed mnemonic drops for GC on unmount), envelope-missing redirect onAsbImportKeyScreen, anduseHeaderHeight()replacing theHEADER_HEIGHT = 50magic.fix(onboarding):syncAsbImportBackupScreen's localloadedFileto the store'senvelope— when the wizard wipes the store post-import, the "Pasted backup" card now disappears so the user can't loop through Key→Backup with a stale state.Test plan
pnpm pre-push --no-fail-on-error— all checks green (lint, typecheck, formatter, copyright headers, i18n, secrets)onboarding-import-asb.test.tsx— 8/8 (1 new regression test for the back-nav scenario)onboarding-import-algo25.test.tsx— 4/4onboarding-import-hd.test.tsx— 7/7backup.test.tsx— 5/5useMnemonicWordEntry.spec.ts— 12 scenarios covering paste distribution (multiple separators), clipboard fallback (incl. iOS autocomplete regression), focus advancement at boundaries, error-callback wiringMnemonicSuggestionBar.spec.tsx— render, empty-state hide, tap callbacksplitMnemonic.spec.ts— 5 separator-mix variants